package gui

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"fyne.io/fyne/v2"
)

const (
	maxUploadSize       = 1024 * 1024 * 1024 // 1 GiB safety limit
	formMemoryThreshold = 64 << 20           // 64 MiB before spilling to disk
)

// uploadServer 为在浏览器中上传文件提供本地HTTP服务
type uploadServer struct {
	app      *App
	server   *http.Server
	listener net.Listener
	baseURL  string
	tempRoot string
}

// newUploadServer 创建并启动上传服务
func newUploadServer(app *App) (*uploadServer, error) {
	tempRoot, err := os.MkdirTemp("", "cos-upload-*")
	if err != nil {
		return nil, fmt.Errorf("创建上传临时目录失败: %w", err)
	}

	us := &uploadServer{
		app:      app,
		tempRoot: tempRoot,
	}

	if err := us.start(); err != nil {
		os.RemoveAll(tempRoot)
		return nil, err
	}

	return us, nil
}

func (us *uploadServer) start() error {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return fmt.Errorf("监听端口失败: %w", err)
	}

	us.listener = listener
	us.baseURL = fmt.Sprintf("http://%s", listener.Addr().String())

	mux := http.NewServeMux()
	mux.HandleFunc("/", us.handleIndex)
	mux.HandleFunc("/upload", us.handleUpload)

	us.server = &http.Server{
		Handler:        mux,
		ReadTimeout:    5 * time.Minute,
		WriteTimeout:   5 * time.Minute,
		MaxHeaderBytes: 1 << 20,
	}

	go func() {
		if err := us.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Printf("上传服务异常: %v", err)
		}
	}()

	return nil
}

// URL 返回上传页面的URL
func (us *uploadServer) URL() string {
	return us.baseURL
}

// Stop 停止上传服务
func (us *uploadServer) Stop(ctx context.Context) error {
	if us.server == nil {
		return nil
	}

	err := us.server.Shutdown(ctx)

	if us.listener != nil {
		_ = us.listener.Close()
	}

	if us.tempRoot != "" {
		_ = os.RemoveAll(us.tempRoot)
	}

	return err
}

func (us *uploadServer) handleIndex(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, _ = io.WriteString(w, uploadPageHTML)
}

func (us *uploadServer) handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	if us.app.client == nil {
		us.writeError(w, http.StatusServiceUnavailable, fmt.Errorf("COS 未连接，请先在主界面完成配置"))
		return
	}

	if err := r.ParseMultipartForm(formMemoryThreshold); err != nil {
		us.writeError(w, http.StatusBadRequest, fmt.Errorf("解析上传内容失败: %w", err))
		return
	}

	files := r.MultipartForm.File["files"]
	if len(files) == 0 {
		us.writeError(w, http.StatusBadRequest, fmt.Errorf("未找到待上传文件"))
		return
	}

	var uploaded []string
	for _, fh := range files {
		if fh.Size > maxUploadSize {
			us.writeError(w, http.StatusBadRequest, fmt.Errorf("文件 %s 超出大小限制", fh.Filename))
			return
		}

		src, err := fh.Open()
		if err != nil {
			us.writeError(w, http.StatusInternalServerError, fmt.Errorf("读取文件 %s 失败: %w", fh.Filename, err))
			return
		}

		sanitizedName := sanitizeFilename(fh.Filename)
		tempDir, err := os.MkdirTemp(us.tempRoot, "item-*")
		if err != nil {
			src.Close()
			us.writeError(w, http.StatusInternalServerError, fmt.Errorf("创建临时目录失败: %w", err))
			return
		}

		tempPath := filepath.Join(tempDir, sanitizedName)
		dst, err := os.Create(tempPath)
		if err != nil {
			src.Close()
			_ = os.RemoveAll(tempDir)
			us.writeError(w, http.StatusInternalServerError, fmt.Errorf("创建临时文件失败: %w", err))
			return
		}

		if _, err := io.Copy(dst, src); err != nil {
			src.Close()
			dst.Close()
			_ = os.RemoveAll(tempDir)
			us.writeError(w, http.StatusInternalServerError, fmt.Errorf("写入临时文件失败: %w", err))
			return
		}

		src.Close()
		dst.Close()

		uploaded = append(uploaded, sanitizedName)
		cleanupDir := tempDir

		fyne.Do(func() {
			us.app.setStatus(fmt.Sprintf("已接收文件: %s，准备上传", sanitizedName))
		})

		us.app.fileOperations.UploadTempFile(tempPath, sanitizedName, func() {
			_ = os.RemoveAll(cleanupDir)
		})
	}

	us.writeJSON(w, http.StatusOK, map[string]interface{}{
		"success":  true,
		"uploaded": uploaded,
		"message":  "文件已接收，将自动上传至 COS",
	})
}

func (us *uploadServer) writeError(w http.ResponseWriter, status int, err error) {
	log.Printf("上传服务错误: %v", err)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(map[string]interface{}{
		"success": false,
		"message": err.Error(),
	})
}

func (us *uploadServer) writeJSON(w http.ResponseWriter, status int, payload interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}

func sanitizeFilename(name string) string {
	if name == "" {
		return "unnamed"
	}

	name = strings.ReplaceAll(name, "\\", "/")
	name = path.Base(name)
	name = strings.TrimSpace(name)
	if name == "." || name == "/" {
		return "unnamed"
	}

	return name
}

const uploadPageHTML = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>COS 文件上传</title>
  <style>
    body {
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f5f5f5;
    }
    .container {
      max-width: 720px;
      margin: 60px auto;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 12px 32px rgba(0,0,0,0.12);
      padding: 32px;
    }
    h1 {
      margin-top: 0;
      font-size: 28px;
      color: #333;
    }
    p {
      color: #555;
    }
    #drop-zone {
      border: 2px dashed #7c5cff;
      border-radius: 12px;
      padding: 48px 24px;
      text-align: center;
      color: #7c5cff;
      background: rgba(124, 92, 255, 0.06);
      transition: all 0.2s ease;
      cursor: pointer;
    }
    #drop-zone.dragover {
      background: rgba(124, 92, 255, 0.15);
      border-color: #5a3fff;
    }
    #file-input {
      display: none;
    }
    #status {
      margin-top: 24px;
      list-style: none;
      padding: 0;
    }
    #status li {
      background: #f0f3ff;
      border-left: 4px solid #7c5cff;
      margin-bottom: 12px;
      padding: 12px 16px;
      border-radius: 6px;
      color: #333;
    }
    #status li.error {
      background: #ffecec;
      border-left-color: #ff5252;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>上传文件到 COS</h1>
    <p>拖拽文件到下方区域，或点击选择文件。文件将暂存于临时目录并自动上传。</p>
    <div id="drop-zone">
      <strong>拖拽文件到此，或点击浏览本地文件</strong>
      <br><br>
      <span>支持一次上传多个文件</span>
    </div>
    <input type="file" id="file-input" multiple>
    <ul id="status"></ul>
  </div>

  <script>
    const dropZone = document.getElementById('drop-zone');
    const fileInput = document.getElementById('file-input');
    const statusList = document.getElementById('status');

    const appendStatus = (message, isError = false) => {
      const item = document.createElement('li');
      item.textContent = message;
      if (isError) {
        item.classList.add('error');
      }
      statusList.prepend(item);
    };

    const uploadFiles = async (files) => {
      if (!files.length) {
        return;
      }

      const formData = new FormData();
      Array.from(files).forEach(file => formData.append('files', file));

      appendStatus('正在上传 ' + files.length + ' 个文件…');

      try {
        const response = await fetch('/upload', {
          method: 'POST',
          body: formData
        });

        const result = await response.json();
        if (!response.ok || !result.success) {
          throw new Error(result.message || '上传失败');
        }

        appendStatus('上传成功: ' + result.uploaded.join(', '));
      } catch (err) {
        appendStatus(err.message || '上传失败', true);
      }
    };

    dropZone.addEventListener('click', () => fileInput.click());

    dropZone.addEventListener('dragover', (event) => {
      event.preventDefault();
      dropZone.classList.add('dragover');
    });

    dropZone.addEventListener('dragleave', () => {
      dropZone.classList.remove('dragover');
    });

    dropZone.addEventListener('drop', (event) => {
      event.preventDefault();
      dropZone.classList.remove('dragover');
      uploadFiles(event.dataTransfer.files);
    });

    fileInput.addEventListener('change', (event) => {
      uploadFiles(event.target.files);
      fileInput.value = '';
    });
  </script>
</body>
</html>`
